package cn.wolfcode.shop.redis;

import cn.wolfcode.shop.redis.key.KeyPrefix;
import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**@ConditionalOnBean(RedisConfig.class)
 * 当存在RedisConfig的实例才会创建当前类的实例
 */
@Component
@ConditionalOnBean(RedisConfig.class)
public class RedisServer {
    @Autowired
    private JedisPool jedisPool;

    /**1. 用来往redis中存放登录用户信息,用户退出则失效 应该设置失效时间
     * 2.用来往redis存储商品信息,则商品应该一直存在提供服务,不应该设置失效时间
     * 保存数据到redis中
     * @param keyPrefix 设置进redis里面的key的前缀 存放各种类型信息对应的key前缀
     * @param key       key 和前缀一起使用表示redis中真正的key
     * @param value    保存的值泛型 可以用来存 用户信息,商品信息等
     * @param <T>
     * @return
     */
    public <T> boolean set(KeyPrefix keyPrefix, String key, T value) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            //最终要存储的key是什么  前缀+key
            String realKey = keyPrefix.getPrefix() + key;
            //redis中value只能是String类型的,所以需要将对象转成字符串
            String objStr = beanToString(value);
            //判断有没有设置失效的时间 如果当前存储的信息需要设置失效时间,则 传进来的对象里有设置失效时间
            if (keyPrefix.getExpireSeconds() > 0) {
                //设置了失效时间
                jedis.setex(realKey, keyPrefix.getExpireSeconds(), objStr);
            } else {
                //说明当前信息在redis中一致存在 提供使用
                //未设置失效时间是往redis中设置key和value
                jedis.set(realKey, objStr);
            }
            return true;
        }finally {
            returnToPool(jedis);
        }
    }

    public <T> boolean hset(KeyPrefix keyPrefix, String key,String field, T value) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            //最终要存储的key是什么  前缀+key
            String realKey = keyPrefix.getPrefix() + key;
            //redis中value只能是String类型的,所以需要将对象转成字符串
            String objStr = beanToString(value);
            jedis.hset(realKey,field,objStr);
            return true;
        }finally {
            returnToPool(jedis);
        }
    }
    /**
     * 与set方法一样做到了通用的目的,可以将从redis中获取的string,转化成其真实对象
     * 使用泛型,在调用方法从redis中取值的时候,指定其真实类型
     * 根据真实类型,将数据从字符串转回真实类型对象
     * @param keyPrefix
     * @param key
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> T get(KeyPrefix keyPrefix,String key,Class<T> clazz){
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            //最终要存储的key是什么  前缀+key
            String realKey = keyPrefix.getPrefix() + key;
            String value = jedis.get(realKey);
            T t = stringToBean(value,clazz);
            return t;
        }finally {
            returnToPool(jedis);
        }
    }
    public <T> Map<String,T> hgetAll(KeyPrefix keyPrefix, String key, Class<T> clazz){
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            //最终要存储的key是什么  前缀+key
            String realKey = keyPrefix.getPrefix() + key;
            //取出的map中的key 唯一区分标识,value 为存储的对象的JSON字符串
            Map<String, String> hgetAll = jedis.hgetAll(realKey);
            Map<String,T> resultMap = new HashMap<>();
            Set<Map.Entry<String, String>> entries = hgetAll.entrySet();
            for (Map.Entry<String, String> entry : entries) {
                T t = stringToBean(entry.getValue(),clazz);
                resultMap.put(entry.getKey(),t);
            }

            return resultMap;
        }finally {
            returnToPool(jedis);
        }
    }
    public <T> T hget(KeyPrefix keyPrefix, String key,String field, Class<T> clazz){
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            //最终要存储的key是什么  前缀+key
            String realKey = keyPrefix.getPrefix() + key;
            String hget = jedis.hget(realKey, field);
            T t = stringToBean(hget, clazz);
            return t;
        }finally {
            returnToPool(jedis);
        }
    }
    /**
     * redis中的自增和自减都是原子性的
     * 将redis中的key 的value增加值
     * 此方法是为了与数据库同步,如数据库商品数量减1 则redis对应的商品数量自减
     * @param keyPrefix
     * @param key
     * @return
     */
    public Long incr(KeyPrefix keyPrefix,String key){
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            String realKey = keyPrefix.getPrefix() + key;
            return jedis.incr(realKey);
        }finally {
            if(jedis!=null){
                returnToPool(jedis);
            }
        }
    }

    /**
     * 秒杀后的redis中的库存减少
     * @param keyPrefix
     * @param key
     * @return
     */
    public Long decr(KeyPrefix keyPrefix,String key){
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            String realKey = keyPrefix.getPrefix()+key;
            return jedis.decr(realKey);
        }finally{
            if (jedis!=null){
                returnToPool(jedis);
            }
        }
    }

    /**
     * 删除redis中的指定key
     * realKey = keyPrefix.getPrefix()+key;
     * @param keyPrefix
     * @param key
     * @return
     */
    public Long delete(KeyPrefix keyPrefix,String key){
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            String realKey = keyPrefix.getPrefix()+key;
            return jedis.del(realKey);
        }finally {
            if (jedis!=null){
                returnToPool(jedis);
            }
        }
    }

    /**
     * 判断redis中的指定key是否还存在
     * @param keyPrefix
     * @param key
     * @return
     */
    public boolean exists(KeyPrefix keyPrefix,String key){
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            String realKey = keyPrefix.getPrefix()+key;
            return jedis.exists(realKey);
        }finally {
            if (jedis!=null){
                returnToPool(jedis);
            }
        }
    }

    /**
     * 给redis 的 key 设置过期时间
     * @param keyPrefix
     * @param key
     * @param seconds
     * @return
     */
    public Long expire(KeyPrefix keyPrefix,String key,int seconds){
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            String realKey = keyPrefix.getPrefix()+key;
            return jedis.expire(realKey,seconds);
        }finally {
            //finally代码块中的代码一定会执行,未确保资源等安全关闭 ,
            //将这些重要操作放在finally代码块中
            if (jedis!=null){
                returnToPool(jedis);
            }
        }
    }
    //使用完毕将jedis归还到池子中
    private void returnToPool(Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }

    /**set放做到了通用,使用泛型不写死指定具体对象,可以用来将各种类型的对象设置redis
     * 所以使用时应将其按照具体实际类型转成字符串,存储到redis
     * 将对象转成字符串
     * @param value
     * @param <T>
     * @return
     */
    private <T> String beanToString(T value) {
        if (value == null) {
            return null;
        }
        Class<?> clazz = value.getClass();
        if (clazz == int.class || clazz == Integer.class) {
            return "" + value;
        }
        if (clazz == long.class || clazz == Long.class) {
            return "" + value;
        }
        if (clazz == double.class || clazz == Double.class) {
            return "" + value;
        }
        if (clazz == float.class || clazz == Float.class) {
            return "" + value;
        }
        if (clazz == boolean.class || clazz == Boolean.class) {
            return String.valueOf(value);
        }
        if (clazz == String.class) {
            return (String) value;
        }
        return JSON.toJSONString(value);
    }

    /**
     * 将从redis中取出来的字符串转成他的真实对象类型
     * @param value
     * @param <T>
     * @return
     */
    private <T> T stringToBean(String value,Class<T> clazz) {
        if (value == null) {
            return null;
        }
        if (clazz == int.class || clazz == Integer.class) {
            return (T) Integer.valueOf(value);
        }
        if (clazz == long.class || clazz == Long.class) {
            return (T) Long.valueOf(value);
        }
        if (clazz == double.class || clazz == Double.class) {
            return (T) Double.valueOf(value);
        }
        if (clazz == float.class || clazz == Float.class) {
            return (T) Float.valueOf(value);
        }
        if (clazz == boolean.class || clazz == Boolean.class) {
            return (T) Boolean.valueOf(value);
        }
        if (clazz == String.class) {
            return (T) value;
        }
        return JSON.parseObject(value,clazz);
    }

    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";

    /**
     * 尝试获取分布式锁
     * @param lockKey 锁
     * @param requestId 请求标识
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public  boolean tryGetDistributedLock(String lockKey, String requestId, int expireTime) {
        Jedis jedis = null;
        try{
            jedis = jedisPool.getResource();
            String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
            if (LOCK_SUCCESS.equals(result)) {
                return true;
            }
            return false;
        }finally {
            returnToPool(jedis);
        }
    }
    private static final Long RELEASE_SUCCESS = 1L;

    /**
     * 释放分布式锁
     * @param lockKey 锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public boolean releaseDistributedLock( String lockKey, String requestId) {
        Jedis jedis = null;
        try{
            jedis = jedisPool.getResource();
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
            if (RELEASE_SUCCESS.equals(result)) {
                return true;
            }
            return false;
        }finally {
            returnToPool(jedis);
        }
    }

    public boolean setNx(String key,String value) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            Long aLong = jedis.setnx(key, value);
            if(aLong >0){
                return true;
            }
            return false;
        }finally {
            returnToPool(jedis);
        }

    }
}
